﻿#region Using References

using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Beaker.OpenCube.Drawing;
using Beaker.OpenCube.Dynamic;
using Beaker.OpenCube.Game;
using Beaker.OpenCube.Textures;
using Beaker.OpenCube.WorldViewer.Messages;
using Beaker.Services.Locator;
using GalaSoft.MvvmLight.Messaging;
using Image = System.Windows.Controls.Image;

#endregion

namespace Beaker.OpenCube.WorldViewer.Controls
{
	[TemplatePart( Name = CanvasPartName, Type = typeof( Canvas ) )]
	public class ChunkMapControl
		: Control
	{
		public const string CanvasPartName = "PART_Canvas";

		static ChunkMapControl()
		{
			DefaultStyleKeyProperty.OverrideMetadata( typeof( ChunkMapControl ), new FrameworkPropertyMetadata( typeof( ChunkMapControl ) ) );
		}

		public ChunkMapControl()
		{
			SetTexturePack( ServiceLocator.Current.GetInstance<ITexturePack>() );

			var messenger = ServiceLocator.Current.GetInstance<IMessenger>();
			messenger.Register<TexturePackChangedMessage>( this, OnTexturePackChanged );
		}

		/// <summary>
		/// Gets the property that gets or sets the smart level reader to use for the chunk map control
		/// </summary>
		public static readonly DependencyProperty CurrentWorldProperty =
			DependencyProperty.Register(
				name: "CurrentWorld",
				propertyType: typeof( DynamicWorld ),
				ownerType: typeof( ChunkMapControl ),
				typeMetadata: new PropertyMetadata(
					defaultValue: null,
					propertyChangedCallback: ( obj, args ) =>
						( (ChunkMapControl)obj ).OnCurrentWorldChanged( (DynamicWorld)args.OldValue, (DynamicWorld)args.NewValue )
				)
			);


		/// <summary>
		/// Gets or sets the selected dimension.
		/// </summary>
		/// <value>
		/// The selected dimension.
		/// </value>
		public Dimension SelectedDimension
		{
			get { return (Dimension)GetValue( SelectedDimensionProperty ); }
			set { SetValue( SelectedDimensionProperty, value ); }
		}

		public static readonly DependencyProperty SelectedDimensionProperty =
			DependencyProperty.Register(
				name: "SelectedDimension",
				propertyType: typeof( Dimension ),
				ownerType: typeof( ChunkMapControl ),
				typeMetadata: new PropertyMetadata(
					defaultValue: Dimension.Overworld
				)
			);


		/// <summary>
		/// Gets or sets the smart level reader to use for the chunk map control.
		/// </summary>
		/// <value>
		/// The smart level reader to use for the chunk map control.
		/// </value>
		public DynamicWorld CurrentWorld
		{
			get { return (DynamicWorld)GetValue( CurrentWorldProperty ); }
			set { SetValue( CurrentWorldProperty, value ); }
		}

		/// <summary>
		/// Called when [current world changed].
		/// </summary>
		/// <param name="oldWorld">The old world.</param>
		/// <param name="newWorld">The new world.</param>
		private async void OnCurrentWorldChanged( DynamicWorld oldWorld, DynamicWorld newWorld )
		{
			// clear current dimension so nothing can use it
			CurrentDimension = null;
			if( newWorld != null )
			{
				Dimension selectedDimension = SelectedDimension;

				// when world changes, we need to check if the selected dimension is available
				bool hasDimension = await Task.Run( () =>
				{
					IEnumerable<Dimension> dimensions = newWorld.GetDimensions();
					return dimensions.Any( d => d == selectedDimension );
				} );

				// when selected dimension is not available, switch back to default overworld
				if( !hasDimension )
					SelectedDimension = Dimension.Overworld;

				// now set the Current Dimension correctly
				CurrentDimension = newWorld.OpenDimension( SelectedDimension );
			}
		}

		public static readonly DependencyProperty CurrentDimensionProperty =
			DependencyProperty.Register(
				name: "CurrentDimension",
				propertyType: typeof( DynamicDimension ),
				ownerType: typeof( ChunkMapControl ),
				typeMetadata: new PropertyMetadata(
					defaultValue: null,
					propertyChangedCallback: ( obj, args ) =>
						( (ChunkMapControl)obj ).OnCurrentDimensionChanged( (DynamicDimension)args.OldValue, (DynamicDimension)args.NewValue )
				)
			);

		/// <summary>
		/// Gets or sets the smart dimension reader for the current dimension.
		/// </summary>
		/// <value>
		/// The smart dimension reader for the current dimension.
		/// </value>
		public DynamicDimension CurrentDimension
		{
			get { return (DynamicDimension)GetValue( CurrentDimensionProperty ); }
			set { SetValue( CurrentDimensionProperty, value ); }
		}


		/// <summary>
		/// Called when [current dimension changed].
		/// </summary>
		/// <param name="oldDimension">The old dimension.</param>
		/// <param name="newDimension">The new dimension.</param>
		private void OnCurrentDimensionChanged( DynamicDimension oldDimension, DynamicDimension newDimension )
		{
			Rebuild();
		}

		private async void Rebuild()
		{
			// get dimenension and the painter to use
			DynamicDimension dimension = CurrentDimension;
			ChunkPainter painter = _painter;

			if( dimension != null && _canvas != null )
			{
				Bitmap[,] bitmaps = await Task.Run( () =>
				{
					var bms = new Bitmap[10,10];
					for( int x = 0; x < 10; x++ )
					{
						for( int z = 0; z < 10; z++ )
						{
							Chunk chunk = dimension.GetChunk( x, z );
							if( chunk != null )
								bms[x, z] = painter.CreatePixelChunkImage( chunk );
						}
					}

					return bms;
				} );

				// throw away old data
				if( _canvas != null )
					_canvas.Children.Clear();

				// create new data
				for( int x = 0; x < 10; x++ )
				{
					for( int z = 0; z < 10; z++ )
					{
						Bitmap bitmap = bitmaps[x, z];
						if( bitmap != null )
						{
							var image = new Image
							{
								Source = bitmap.CreateBitmapSource()
							};

							Canvas.SetLeft( image, x * 16 );
							Canvas.SetTop( image, z * 16 );

							_canvas.Children.Add( image );
						}
					}
				}

			}
		}

		private Canvas _canvas;

		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();
			_canvas = GetTemplateChild( CanvasPartName ) as Canvas;
		}

		#region TexturePack

		private ITexturePack _texturePack;
		private ChunkPainter _painter;

		/// <summary>
		/// Called when [texture pack changed].
		/// </summary>
		/// <param name="message">The message.</param>
		private void OnTexturePackChanged( TexturePackChangedMessage message )
		{
			SetTexturePack( message.NewTexturePack );
		}

		/// <summary>
		/// Sets the texture pack.
		/// </summary>
		/// <param name="texturePack">The texture pack.</param>
		private void SetTexturePack( ITexturePack texturePack )
		{
			_texturePack = texturePack;

			if( texturePack == null )
				_painter = null;
			else
				_painter = new ChunkPainter( texturePack );

			Rebuild();
		}

		#endregion
	}
}
